#include "BBS2chProxyFormData.h"
#include <stdio.h>
#include <stdlib.h>

std::string BBS2chProxyFormData::decodeURIComponent(const char *input, size_t inputLength, bool decodePlus)
{
	std::string output;
	for (size_t i=0; i<inputLength; i++) {
		if (input[i] == '%') {
			if (i < inputLength - 2) {
				char from[3];
				char *end;
				from[0] = input[i+1];
				from[1] = input[i+2];
				from[2] = 0;
				unsigned long n = strtoul(from, &end, 16);
				if (n < 256 && end == from+2) {
					output.append(1, n);
					i += 2;
					continue;
				}
			}
		}
		else if (decodePlus && input[i] == '+') {
			output.append(" ");
			continue;
		}
		output.append(1, input[i]);
	}
	return output;
}

static std::string encodeURIComponent(const std::string &input, bool spaceAsPlus)
{
	std::string output;
	for (size_t i=0; i<input.size(); i++) {
		unsigned char c = (unsigned char)input[i];
		if ((c >= '0' && c <= '9') ||
			(c >= 'A' && c <= 'Z') ||
			(c >= 'a' && c <= 'z') ||
			(c == '-') || (c == '.') || (c == '_')) {
			output.append(1, c);
		}
		else if (c == ' ' && spaceAsPlus) {
			output.append("+");
		}
		else {
			char percentEncoded[4];
			snprintf(percentEncoded, 4, "%%%02X", c);
			output.append(percentEncoded);
		}
	}
	return output;
}

std::string BBS2chProxyFormData::encodeURIComponent(const char *input, size_t inputLength, bool spaceAsPlus)
{
	return ::encodeURIComponent(std::string(input, inputLength), spaceAsPlus);
}

BBS2chProxyURLEncodedValue::BBS2chProxyURLEncodedValue()
	: _hasValue(true), _hasEncodedValue(true)
{
}

BBS2chProxyURLEncodedValue::BBS2chProxyURLEncodedValue(const std::string &value, bool encoded)
{
	if (value.empty()) {
		_hasValue = true;
		_hasEncodedValue = true;
		return;
	}
	if (encoded) {
		_encodedValue = value;
		_hasValue = false;
		_hasEncodedValue = true;
	} else {
		_value = value;
		_hasValue = true;
		_hasEncodedValue = false;
	}
}

BBS2chProxyURLEncodedValue::BBS2chProxyURLEncodedValue(const char *value, size_t length, bool encoded)
{
	if (length == 0) {
		_hasValue = true;
		_hasEncodedValue = true;
		return;
	}
	if (encoded) {
		_encodedValue = std::string(value, length);
		_hasValue = false;
		_hasEncodedValue = true;
	} else {
		_value = std::string(value, length);
		_hasValue = true;
		_hasEncodedValue = false;
	}
}

const std::string& BBS2chProxyURLEncodedValue::get()
{
	if (!_hasValue) {
		_value = BBS2chProxyFormData::decodeURIComponent(_encodedValue.data(), _encodedValue.size(), true);
		_hasValue = true;
	}
	return _value;
}

const std::string& BBS2chProxyURLEncodedValue::getEncoded()
{
	if (!_hasEncodedValue) {
		_encodedValue = encodeURIComponent(_value, true);
		_hasEncodedValue = true;
	}
	return _encodedValue;
}

void BBS2chProxyURLEncodedValue::set(const std::string &value, bool encoded)
{
	if (value.empty()) {
		_value.clear();
		_encodedValue.clear();
		_hasValue = true;
		_hasEncodedValue = true;
		return;
	}
	if (encoded) {
		_encodedValue = value;
		_hasValue = false;
		_hasEncodedValue = true;
	} else {
		_value = value;
		_hasValue = true;
		_hasEncodedValue = false;
	}
}

bool BBS2chProxyURLEncodedValue::empty()
{
	return !((_hasValue && !_value.empty()) || (_hasEncodedValue && !_encodedValue.empty()));
}

BBS2chProxyURLEncodedValue& BBS2chProxyURLEncodedValue::operator=(const std::string &value)
{
	set(value);
	return *this;
}

BBS2chProxyFormData::BBS2chProxyFormData(const char *data, size_t length)
	: _dirty(true)
{
	if (length == 0) return;
	const char *ptr = data;
	while (1) {
		const char *tmp = ptr;
		while (*tmp != '=' && tmp - data < length) tmp++;
		std::string key = decodeURIComponent(ptr, tmp-ptr, true);
		if (tmp - data >= length) {
			bool inserted = _fields.insert(std::make_pair(key, BBS2chProxyURLEncodedValue())).second;
			if (inserted) _order.push_back(key);
			break;
		}
		tmp++;
		ptr = tmp;
		while (*tmp != '&' && tmp - data < length) tmp++;
		BBS2chProxyURLEncodedValue value(ptr, tmp-ptr, true);
		bool inserted = _fields.insert(std::make_pair(key, value)).second;
		if (inserted) _order.push_back(key);
		if (tmp - data >= length) {
			break;
		}
		ptr = tmp + 1;
	}
}

std::string BBS2chProxyFormData::get(const std::string &key)
{
	iterator it = _fields.find(key);
	if (it == _fields.end()) return "";
	return it->second.get();
}

std::string BBS2chProxyFormData::getEncoded(const std::string &key)
{
	iterator it = _fields.find(key);
	if (it == _fields.end()) return "";
	return it->second.getEncoded();
}

void BBS2chProxyFormData::append(const std::string &key, const std::string &value, bool encoded)
{
	const std::string &decodedKey = encoded ? decodeURIComponent(key.data(), key.size(), true) : key;
	BBS2chProxyURLEncodedValue encodedValue(value, encoded);
	bool inserted = _fields.insert(std::make_pair(decodedKey, encodedValue)).second;
	if (inserted) {
		_order.push_back(decodedKey);
		_dirty = true;
	}
}

void BBS2chProxyFormData::set(const std::string &key, const std::string &value, bool encoded)
{
	const std::string &decodedKey = encoded ? decodeURIComponent(key.data(), key.size(), true) : key;
	iterator it = _fields.find(decodedKey);
	if (it == _fields.end()) {
		BBS2chProxyURLEncodedValue encodedValue(value, encoded);
		_fields.insert(std::make_pair(decodedKey, encodedValue));
		_order.push_back(decodedKey);
	}
	else it->second.set(value, encoded);
	_dirty = true;
}

bool BBS2chProxyFormData::has(const std::string &key)
{
	return _fields.find(key) != _fields.end();
}

void BBS2chProxyFormData::remove(const std::string &key)
{
	if (_fields.erase(key) > 0) {
		for (std::vector<std::string>::iterator it = _order.begin(); it != _order.end();) {
			if (*it == key) it = _order.erase(it);
			else it++;
		}
		_dirty = true;
	}
}

void BBS2chProxyFormData::reorder(const std::vector<std::string> &order)
{
	std::map<std::string, BBS2chProxyURLEncodedValue> newFields;
	std::vector<std::string> newOrder;
	for (std::vector<std::string>::const_iterator it = order.begin(); it != order.end(); it++) {
		const std::string &key = *it;
		iterator it2 = _fields.find(key);
		if (it2 != _fields.end()) {
			newFields.insert(std::make_pair(key, it2->second));
			newOrder.push_back(key);
			_fields.erase(key);
		}
	}
	for (std::vector<std::string>::iterator it = _order.begin(); it != _order.end(); it++) {
		const std::string &key = *it;
		iterator it2 = _fields.find(key);
		if (it2 != _fields.end()) {
			newFields.insert(std::make_pair(key, it2->second));
			newOrder.push_back(key);
			_fields.erase(key);
		}
	}
	_dirty = true;
	_fields = newFields;
	_order = newOrder;
}

const std::string& BBS2chProxyFormData::toString()
{
	if (!_dirty) return _body;
	_body.clear();
	for (std::vector<std::string>::iterator it = _order.begin(); it != _order.end(); it++) {
		const std::string &key = *it;
		const std::string &value = _fields.find(key)->second.getEncoded();
		if (!_body.empty()) _body += '&';
		_body += ::encodeURIComponent(key, true);
		_body += '=';
		_body += value;
	}
	_dirty = false;
	return _body;
}

size_t BBS2chProxyFormData::size()
{
	return toString().size();
}

BBS2chProxyFormData::iterator BBS2chProxyFormData::begin()
{
	_dirty = true;
	return _fields.begin();
}

BBS2chProxyFormData::iterator BBS2chProxyFormData::end()
{
	_dirty = true;
	return _fields.end();
}

std::string BBS2chProxyFormData::operator[](const std::string &key)
{
	return get(key);
}
